--[[	

Entities are the root object (equivalant to particles in QF) to represent 
attritubes (equivalant to states in QF) with superosition

Quantom Physics (QF) if just a source of inspiration and is applyed to entities instead of particles here. 
The size of Entities in our case is far bigger than a particle : Body, Props, Locations...  
Such big objects are offcourse governed by clasical physics in our real world, but in the context of 
interactive story telling, this metaphore was very practical.
I was also inspired by some scientist that wonder if, thanks to QF, our consciousness co-author the world (past & present)
in which we are living. Some of those ideas are used in this interactive story system. 
This explain the changement of scale of the QF principles

Entities can be composed of two things : 

	- arrays of other entity references (this form a graph of entities)
	- properties (with superposition of values)
	- variables (hiden variables)

Arrays of entity references are lua table inside the Entity table
Variables are standard lua variables in the Entity table, they have single value as usual

Properties are stored in the properties table of the Entity table
Properties can multiple values, each ones with a non normalised probability

Example of an property named "exempleProperty" that have a 50% probability to be true 
and 50% probability to be false :

	self.addProperty("ExempleProperty")
	self.properties["ExempleProperty"][false] = 1
	self.properties["ExempleProperty"][true] = 1

As properties multiple values are stored in a table, they can be copied very simply :

	local destinationProperty = self.property["exempleProperty"]

Properties can also be entities as in

	self.addProperty("MyBestFriend")
	self.properties["MyBestFriend"][tomTheEntity] = 1
	self.properties["MyBestFriend"][jeryTheEntity] = 0.5	

The redefinition of the index function allow this syntaxic sugar :
(this is only possible outside an initialize function)

	exampleEntity.MyBestFriend[tomTheEntity] = 1
	exampleEntity.MyBestFriend[jeryTheEntity] = 0.5
]]

Entity = class("Entity") 
Entity:include(Indexable)


-- statics

Entity.assertPropertyExistance = true
Entity.byTag = {}

--- members

function Entity:initialize(type, name)

	self.name = name						-- name must be defined before 	registerEntityType
	self:registerEntityType(type)	
	self.type = type						-- type must be defined after registerEntityType
	
	self.isEntity = true
	self.tags = {}
	self.properties = {}
	self.decoheredProperties = {}
	self.interactors = {}
	self.lastObservationBeat = -1
end  

---

function Entity:registerEntityType(type)

	if type ~= self.type then
	
		assert(
			(
				(not gField.entityTypes[type]) or 
				(not gField.entityTypes[type][self.name])
			),
			"Two entities has the same name : " .. type .. " " .. self.name
		)
		
		if not gField.entityTypes[type] then
			gField.entityTypes[type] = {}
		end
		
        gField.entityTypes[type][self.name] = self
	end
end

---

function Entity:onRemoval()

	for key, value in pairs(self) do
	
		self.key = nil
	end	
end

---

function Entity:isAKindOf(type)

	assert(gField.entityTypes[type], "isAKindOf error : Type is not declared")
	
	return gField.entityTypes[type][self.name]
end

---

function Entity:addProperty(propertyName, minimumIntegerValue, maximumIntegerValue)

	self.properties[propertyName] = {}	
	self.decoheredProperties[propertyName] = {}	
	
	if minimumIntegerValue
	then
		for i = minimumIntegerValue, maximumIntegerValue
		do
			self.properties[propertyName][i] = 1
		end	
	end
end

---

function Entity:setPropertyFromListTable(propertyName, listTable)

	for i, possibility in ipairs(listTable) do
	
		self.properties[propertyName][possibility[1]] = possibility[2]
	end
end

---

function Entity:index(propertyName)

	if Entity.assertPropertyExistance then
	
		assert (self.properties[propertyName], "Accessing property '" .. propertyName .. "' that is not existing in " .. self.type .. " " .. self.name)
		assert (type(self.properties[propertyName]) == "table", "Property is not a table of possibilities")
	end

	local value = self.properties[propertyName]
	
	--gLogger:log("Access property " .. propertyName .. " in " .. self.type .. " " .. self.name .. " returning : " .. tostring(value))

	return value
end

---

function Entity:hasProperties()

	return next(self.properties)
end

---

function Entity:isPropertyExist(propertyName)

	Entity.assertPropertyExistance = false
	local exist = self.properties[propertyName]
	Entity.assertPropertyExistance = true
	
	return exist
end

---

function Entity:isVariableExist(variableName)

	Entity.assertPropertyExistance = false
	local exist = self[variableName]
	Entity.assertPropertyExistance = true
	
	return exist
end

---

function Entity:isPropertyCollasped(propertyName)

	local propertyValue, propertyProbability = next(self.properties[propertyName])

	return 
	(
		(propertyValue ~= nil) and
		(not next(self.properties[propertyName], propertyValue))
	)
end

---

function Entity:areAllPropertiesCollapsed()

	for propertyKey, propertyPossibilities in pairs(self.properties) do
	
		local value, probability = next(propertyPossibilities)
		
		if next(propertyPossibilities, value) then
			return false
		end
	end
	
	return true
end

---

function Entity:addTag(tag)

	self.tags[tag] = true
	
	if not Entity.byTag[tag]
	then
		Entity.byTag[tag] = {}
	end
	
	Entity.byTag[tag][self] = true
end

---

function Entity:removeTag(tag)

	self.tags[tag] = nil
	
	Entity.byTag[tag][self] = nil
end

---

function Entity:hasTag(tag)

	return self.tags[tag]
end

---

function Entity:printProperties()

	print("--- " .. self.type .. " " .. self.name .. " properties ---")
	debug.printTable(self.properties, 3)
end

---

function Entity:getCollapsedProperty(propertyName)
	
	local value, probability = next(self.properties[propertyName])
	
	assert(not next(self.properties[propertyName], value), "trying to get a collapsed property that is not yet collasped")
	
	return value
end

---

function Entity:observe()

	gLogger:log("Observe " .. self.type .. " " .. self.name)

	for propertyName, propertyPossibilities in pairs(self.properties) do
	
		if not self:isPropertyCollasped(propertyName) then
		
			self:collapseProperty(propertyName)
		end
	end
	
	self.lastObservationBeat = gField.beat
end

---

function Entity:addInteractor(propertyName, interactor)

	if not self.interactors[propertyName]
	then
		self.interactors[propertyName] = {}
	end
	
	self.interactors[propertyName][#(self.interactors[propertyName]) + 1] = interactor
end

---

function Entity:getRandomPossibility(propertyName, potentialValues)

	if not potentialValues
	then
	 potentialValues = self.properties[propertyName]
	end
	

	-- roulette wheel possibility selection

	local totalProbabilties 		= 0
	local currentWheelPosition		= 0
	local marblePosition, biased	= gRandom:get({self.type, self.name, propertyName})
	
	if biased 
	then
	
		return marblePosition		-- return the biased random value during unit testing
	end
	
	for potentialValue, valueProbability in pairs(potentialValues) 
	do
		totalProbabilties = totalProbabilties + valueProbability
	end		
	
	for potentialValue, valueProbability in pairs(potentialValues) 
	do
		currentWheelPosition = currentWheelPosition + valueProbability / totalProbabilties
			
		if marblePosition < currentWheelPosition 
		then
			return potentialValue
		end
	end
end

---

function Entity:getDecoheredProperty(propertyName)

	self.decoheredProperties[propertyName] = {}
	
	if self.interactors[propertyName]
	then
		local decoheredPotentialValues = false
		
		for i, interactor in ipairs(self.interactors[propertyName])
		do
			if not decoheredPotentialValues
			then
				decoheredPotentialValues = interactor:decohere(self.properties[propertyName])
			else
				decoheredPotentialValues = interactor:decohere(decoheredPotentialValues)
			end
			
			assert(next(decoheredPotentialValues), "Potential vales set was completly cleared during decoherence")
		end
		
		self.decoheredProperties[propertyName] = decoheredPotentialValues
	else
		self.decoheredProperties[propertyName] = self.properties[propertyName]
	end
	
	return self:getRandomPossibility(propertyName, self.decoheredProperties[propertyName])
end

---

function Entity:collapseProperty(propertyName, newValue)

	local wasCollapsed = self:isPropertyCollasped(propertyName)
	
	if newValue == nil 
	then
			if wasCollapsed
			then
				return
			end
				
			newValue = self:getDecoheredProperty(propertyName)
	end
	
	if newValue ~= nil 
	then
		self.properties[propertyName] = {}
		self.properties[propertyName][newValue] = 1
		self.decoheredProperties[propertyName] = {}
		self.decoheredProperties[propertyName][newValue] = 1
		
		gLogger:log(self.type .. " " .. self.name .. " collapsed " .. propertyName .. " to " .. tostring(newValue))
	end	
	
	if 
		(not wasCollapsed) and
		gDramaManager
	then
		gDramaManager:onPropertyCollapsed(self, propertyName)
	end
end

---

function Entity:reduceProperty(propertyName, observedvalue)

	if not self:isPropertyCollasped(propertyName) 
	then
		if self:getDecoheredProperty(propertyName) == observedvalue 
		then
		
			-- collapse

			self:collapseProperty(propertyName, observedvalue)
			
			return true
		else
		
			-- reduce
			
			self.properties[propertyName][observedvalue] = nil
			self.decoheredProperties[propertyName][observedvalue] = nil
			
			gLogger:log(self.type .. " " .. self.name .. " " .. "reduced by removing " .. propertyName .. tostring(observedvalue))
		
			if self:isPropertyCollasped(propertyName)				-- does the reduction indirectly collapsed the attribute ? 
			then  		
				gDramaManager:onPropertyCollapsed(self, propertyName)
				
				return true
			end
		end
	end
	
	return false
end
